package cc.rboot.inject.register;

import cc.rboot._core.holder.AppContextHolder;
import cc.rboot.inject.annotation.InjectMapper;
import cc.rboot.inject.annotation.InjectMappers;
import cc.rboot.inject.core.IMapperInjector;
import cc.rboot.inject.core.InjectConstant;
import cc.rboot.inject.core.InjectTokenStore;
import cc.rboot.inject.helper.InjectServiceHelper;
import cc.rboot.token.annotation.RequireToken;
import cc.rboot.token.core.IToken;
import cc.rboot.token.tokens.DefaultToken;
import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class InjectMapperRegistry implements CommandLineRunner, EnvironmentAware {

    private Environment environment;

    @Override
    public void run(String... args) throws Exception {
        System.out.println("Inject Start...");
        String RESOURCE_PATTERN = "/**/*.class";
        String BASE_PACKAGE = environment.getProperty("rboot.inject.base-package");
        if (BASE_PACKAGE == null) {
            throw new Exception("请配置 rboot.inject.base-package");
        }
        ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
        String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
                ClassUtils.convertClassNameToResourcePath(BASE_PACKAGE) + RESOURCE_PATTERN;
        Resource[] resources = resourcePatternResolver.getResources(pattern);
        MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
        for (Resource resource : resources) {
            MetadataReader reader = readerFactory.getMetadataReader(resource);
            String classname = reader.getClassMetadata().getClassName();
            Class<?> controllerClass = Class.forName(classname);

            if (!controllerClass.isAnnotationPresent(RestController.class)) {
                continue;
            }

            InjectMappers injectMappers = controllerClass.getAnnotation(InjectMappers.class);
            if (injectMappers == null) {
                InjectMapper injectMapper = controllerClass.getAnnotation(InjectMapper.class);
                if (injectMapper != null) {
                    for (Class<? extends IMapperInjector> mapperClass : injectMapper.mapper()) {
                        registerForClass(controllerClass, mapperClass, getTokenClass(controllerClass, injectMapper.token()));
                    }
                    continue;
                }
            }

            if (null != injectMappers && null != injectMappers.value() && injectMappers.value().length > 0) {
                for (InjectMapper injectMapper : injectMappers.value()) {
                    for (Class<? extends IMapperInjector> mapperClass : injectMapper.mapper()) {
                        registerForClass(controllerClass, mapperClass, getTokenClass(controllerClass, injectMapper.token()));
                    }
                }
            }
        }
        System.out.println("Inject Finished.");
        System.out.println("System Started.");
    }

    private Class<? extends IToken> getTokenClass(Class<?> controllerClass, Class<? extends IToken> priorityTokenClass) {
        Class<? extends IToken> tokenClass = priorityTokenClass;
        if (tokenClass == DefaultToken.class) {
            RequireToken requireToken = controllerClass.getAnnotation(RequireToken.class);
            if (null != requireToken) {
                tokenClass = requireToken.value()[0];
            }
        }
        return tokenClass;
    }

    private static boolean supportsRegister(Method method) {
        for (Class<? extends Annotation> supportClass : InjectConstant.SUPPORT_MAPPING_CLASSES) {
            if (method.isAnnotationPresent(supportClass)) {
                return true;
            }
        }
        return false;
    }


    private static void registerForClass(Class controllerClass, Class<? extends IMapperInjector> mapperClass, Class<? extends IToken> tokenClass) throws Exception {
        for (Method method : mapperClass.getMethods()) {
            if (supportsRegister(method)) {
                registerForMethod(controllerClass, mapperClass, method, tokenClass);
            }
        }
    }

    private static void registerForMethod(Class controllerClass, Class<? extends IMapperInjector> mapperClass, Method method, Class<? extends IToken> tokenClass) throws Exception {
        if (null == method) {
            return;
        }
        String[] postfix = null;
        RequestMethod[] requestMethods = null;

        for (Annotation annotation : method.getAnnotations()) {
            if ((annotation instanceof PatchMapping)) {
                RequestMapping father = annotation.annotationType().getAnnotation(RequestMapping.class);
                requestMethods = father.method();
                postfix = ((PatchMapping) annotation).path();
            } else if ((annotation instanceof PutMapping)) {
                RequestMapping father = annotation.annotationType().getAnnotation(RequestMapping.class);
                requestMethods = father.method();
                postfix = ((PutMapping) annotation).path();
            } else if ((annotation instanceof DeleteMapping)) {
                RequestMapping father = annotation.annotationType().getAnnotation(RequestMapping.class);
                requestMethods = father.method();
                postfix = ((DeleteMapping) annotation).path();
            } else if ((annotation instanceof PostMapping)) {
                RequestMapping father = annotation.annotationType().getAnnotation(RequestMapping.class);
                requestMethods = father.method();
                postfix = ((PostMapping) annotation).path();
            } else if ((annotation instanceof GetMapping)) {
                RequestMapping father = annotation.annotationType().getAnnotation(RequestMapping.class);
                requestMethods = father.method();
                postfix = ((GetMapping) annotation).path();
            } else if ((annotation instanceof RequestMapping)) {
                requestMethods = ((RequestMapping) annotation).method();
                postfix = ((RequestMapping) annotation).path();
            }
        }

        if (postfix.length == 0) {
            postfix = new String[]{""};
        }
        RequestMapping classRequestMapping = (RequestMapping) controllerClass.getAnnotation(RequestMapping.class);

        String[] basePath = classRequestMapping.path();

        List<String> pathsTmp = new ArrayList<>();
        for (int i = 0; i < basePath.length; i++) {
            for (int j = 0; j < postfix.length; j++) {
                pathsTmp.add(basePath[i] + postfix[j]);
            }
        }

        String[] paths = new String[pathsTmp.size()];
        for (int i = 0; i < pathsTmp.size(); i++) {
            paths[i] = pathsTmp.get(i);
        }

        if (requestMethods == null) {
            return;
        }

        RequestMappingInfo requestMappingInfo = RequestMappingInfo
                .paths(paths)
                .methods(requestMethods)
                .build();

        RequestMappingHandlerMapping requestMappingHandlerMapping = AppContextHolder.getBean(RequestMappingHandlerMapping.class);

        IMapperInjector controller = mapperClass.newInstance();
        IService service = InjectServiceHelper.injectService(controllerClass);
        controller.setService(service);
        requestMappingHandlerMapping.registerMapping(requestMappingInfo, controller, method);

        if (null != tokenClass) {
            for (RequestMethod requestMethod : requestMethods) {
                for (String path : paths) {
                    System.out.println("Inject Endpoint : [ " + path + " ] [ " + requestMethod + " ]");
                    InjectTokenStore.store(requestMethod.name() + "_" + path, tokenClass);
                }
            }
        }
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

}
